﻿#include "CentralCache.h"
#include "PageCache.h"
CentralCache CentralCache::_sInst;
//获取⼀个⾮空的span
Span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	//查看当前spanlist是否还有未分配的span
	Span* it = list.Begin();
	while (it!= list.End())
	{
		if (it->_freeList != nullptr)
		{
			return it;
		}
		else
		{
			it = it->_next;  //应该是指向下一个，不然返回return的话就会置空
		}
	}
	// 先把central cache的桶锁解掉，这样如果其他线程释放内存对象回来，不会阻塞
	list._mtx.unlock();
	PageCache::Getinstance()->_pageMtx.lock();

	//到这里说明没有空闲的span,只能找page cache要
	Span* span=PageCache::Getinstance()->NewSpan(SizeClass::NumMovePage(size));
	span->_isuse = true;
	span->_objSize = size;
	PageCache::Getinstance()->_pageMtx.unlock();

	//计算span的大块内存起始地址和大块内存的大小
	char* start = (char*)(span->_page_Id << PAGE_SHIFT);
	size_t bytes = span->_n << PAGE_SHIFT;
	char* end = start + bytes;
	//把大块内存切成自由链表链接起来
	   //1.先切一块下来做头，方便尾插
	span->_freeList = start;
	start += size; //切掉一块
	void* tail = span->_freeList; //指向切掉那块
	while (start < end)
	{
		NextObj(tail) = start; //此时start就在后面一块
		tail = NextObj(tail); //tail=start
		start += size;
	}
	NextObj(tail) = nullptr;
	//切好span以后，需要把span挂到桶里面去的时候，再加锁
	list._mtx.lock();
	//将切好的span头插到spanList
	list.PushFront(span);
	return span;
}

// 从中⼼缓存获取⼀定数量的对象给thread cache
//如果batchNum过大,有多少拿多少
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size); //获取位置，是哪个桶
	_spanLists[index]._mtx.lock();//上锁   central cache是所有线程共享的，我们在访问central cache中的哈希桶时，需要先给对应的哈希桶加上桶锁，在获取到对象后再将桶锁解掉。
	//在对应哈希桶中获取一个非空的span
	Span* span = GetOneSpan(_spanLists[index], size);
	assert(span); //span不为空
	assert(span->_freeList); //span当中的自由链表也不为空
	//for (size_t i = 0; i <= batchNum-1; i++) 缺陷，如果batchNum过大，span的桶里面不够就会崩溃
	//{
	//	end = NextObj(end);	 //更新end位置
	//}

	//从span中获取batchNum个对象
	//如果不够batchNum个，有多少拿多少
	start = span->_freeList;
	end = start;
	size_t i = 0;
	size_t actualNum = 1; //实际申请到的
	while (i < batchNum - 1 && NextObj(end) != nullptr)
	{
		end = NextObj(end);
		++i;
		++actualNum;
	}
	span->_freeList = NextObj(end); 
	NextObj(end) = nullptr; //切完内存后，最后一个指针要置空

	span->_useCount += actualNum;

	_spanLists[index]._mtx.unlock();//解锁

	return actualNum;
}
//span的_useCount减到0，分配出去的对象全部都还回来了，进一步还给page cache
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanLists[index]._mtx.lock();		//加桶锁
	while (start)
	{
		void* next = NextObj(start); //记录下一个
		Span* span = PageCache::Getinstance()->MapObjectToSpan(start);
		//将对象头插到span的自由链表
		NextObj(start) = span->_freeList;
		span->_freeList = start;

		span->_useCount--; //代表收回一个，更新计数
		if (span->_useCount == 0) //分配出去的已经全部回收完了
		{
			//此时这个span就可以再回收给page cache，page cache可以再尝试去做前后页的合并
			_spanLists[index].Erase(span); //因为page cache中的span是不需要切分的，所以要置空
			span->_freeList = nullptr;//自由链表置空 
			span->_next = nullptr;
			span->_prev = nullptr;

			//释放span给page cache时，使用page cache的锁就可以了，这时把桶锁解掉
			_spanLists[index]._mtx.unlock();			//解桶锁
			PageCache::Getinstance()->_pageMtx.lock();	//加大锁
			PageCache::Getinstance()->ReleaseSpanToPageCache(span);
			PageCache::Getinstance()->_pageMtx.unlock();//解大锁
			_spanLists[index]._mtx.lock();				//加桶锁
		}
		start = next;
	}

	_spanLists[index]._mtx.unlock();//解桶锁
}
